package util;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
import javax.servlet.http.HttpServletRequest;

/**
 * HTTP、HTTPS 请求工具类
 * 
 * @author ydq
 *
 */
public final class HttpRequest implements AutoCloseable{

	/**
	 * 创建一个Get请求
	 * 
	 * @param url
	 * @return
	 */
	public static HttpRequest get(String url) {
		return new HttpRequest(url, Method.GET);
	}

	/**
	 * 创建一个Post请求
	 * 
	 * @param url
	 * @return
	 */
	public static HttpRequest post(String url) {
		return new HttpRequest(url, Method.POST);
	}
	// 请求方法
	private enum Method{GET, POST}

	// 请求协议
	private enum Protocol{HTTP, HTTPS}

	private String url;// 请求地址
	//默认模拟IE11的UserAgent
	private String userAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko";
	private Method method;// 请求方法
	private Map<String, Object> params;// 请求参数
	private Charset reqCharset = Charset.forName("UTF-8");// 请求编码
	private Charset respCharset = Charset.forName("UTF-8");// 返回的编码
	
	private Protocol protocol;//请求协议

	private String sslKey;// 微信退款API请求证书Key
	private String certPath;// 微信退款API证书文件路径
	private String postData;//Post 请求方式发送的数据
	
	private String savePath;//文件下载保存路径
	
	//私有的构造方法
	private HttpRequest(String url, Method method) {
		super();
		this.url = url;
		this.method = method;
		this.params = new HashMap<>();
		if (url != null && url.toUpperCase().indexOf(Protocol.HTTP.toString()) == 0) {
			protocol = Protocol.HTTP;
			if (url.toUpperCase().indexOf(Protocol.HTTPS.toString()) == 0)
				protocol = Protocol.HTTPS;
		}
	}

	/**
	 * 设置请求编码
	 * @param charset
	 * @return
	 */
	public HttpRequest reqCharset(Charset charset) {
		reqCharset = charset;
		return this;
	}

	/**
	 * 设置返回值编码
	 * @param charset
	 * @return
	 */
	public HttpRequest respCharset(Charset charset) {
		respCharset = charset;
		return this;
	}

	/**
	 * 添加请求参数
	 * @param key
	 * @param value
	 * @return
	 */
	public HttpRequest put(String key, Object value) {
		this.params.put(key, value);
		return this;
	}

	/**
	 * 添加一个Map请求参数
	 * @param params
	 * @return
	 */
	public HttpRequest put(Map<String, Object> params) {
		if (params != null) {
			params.forEach((k, v) -> {
				this.params.put(k, v);
			});
		}
		return this;
	}

	/**
	 * 设置UserAgent
	 * @param userAgent
	 * @return
	 */
	public HttpRequest userAgent(String userAgent) {
		this.userAgent = userAgent;
		return this;
	}
	
	public HttpRequest sslConfig(String sslKey,String certPath){
		this.sslKey = sslKey;
		this.certPath = certPath;
		return this;
	}

	/**
	 * 发送请求 并返回结果
	 * @return
	 */
	public String send() {
		if (protocol!=null) {
			boolean hasMap = !params.keySet().isEmpty();
			//如果是Get方式 或 Post方式但是有需要直接Post输出的参数 则将Map参数设置在字符串上
			if (this.method == Method.GET||this.postData!=null)
				url =  hasMap? (url + (url.indexOf("?") >= 0 ? "&" : "?") + createParamStr()):url;
			//如果是Post方式 并且没有设置需要直接Post输出的参数（非键值对）则将Map参数转化为Post参数
			if(this.method == Method.POST&&hasMap&&this.postData==null){
				this.postData = createParamStr();
			}
			try {
				return request();
			} catch (KeyManagementException | NoSuchAlgorithmException | IOException | UnrecoverableKeyException
					| KeyStoreException | CertificateException e) {
				System.out.println(e.getMessage());
			}
		}
		return null;
	}
	
	/**
	 * 文件下载
	 * @param path 文件下载存储的文字
	 * @return 是否下载成功
	 */
	public boolean download(String path){
		if(path==null) return false;
		savePath = path;
		return "success".equals(send());
	}
	
	/**
	 * 发送带有Post数据的请求（非键值对的Post参数，如腾讯微信创建自定义菜单的接口）
	 * @param postData
	 * @return
	 */
	public String send(String postData) {
		this.postData = postData;
		return send();
	}

	/**
	 * 执行请求的方法
	 * @param protocol 请求协议
	 * @param requestUrl 请求的地址
	 * @return
	 * @throws IOException
	 * @throws KeyManagementException
	 * @throws NoSuchAlgorithmException
	 * @throws KeyStoreException
	 * @throws CertificateException
	 * @throws UnrecoverableKeyException
	 */
	private String request() throws IOException, KeyManagementException,
			NoSuchAlgorithmException, KeyStoreException, CertificateException, UnrecoverableKeyException {
		CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
		URL url = new URL(this.url);
		URLConnection conn = url.openConnection();
		String result = null;
		if (Protocol.HTTPS == protocol) {
			SSLContext sslContext = SSLContext.getInstance("TLSV1.2");
			X509TrustManager[] tm = { new X509TrustManager() {
				@Override
				public X509Certificate[] getAcceptedIssuers() {return null;}
				@Override
				public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
				@Override
				public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
			} };
			if (sslKey == null || certPath == null) {
				sslContext.init(null, tm, new SecureRandom());
			} else {
				// 需要用到证书文件和证书key的时候，专为微信退款请求接口设计
				final char[] kp = sslKey.toCharArray();
				KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
				KeyStore ks = KeyStore.getInstance("PKCS12");
				FileInputStream fileInputStream = new FileInputStream(certPath);
				ks.load(fileInputStream, kp);
				kmf.init(ks, kp);
				sslContext.init(kmf.getKeyManagers(), tm, new SecureRandom());
			}
			SSLSocketFactory ssf = sslContext.getSocketFactory();
			HttpsURLConnection httpsConn = ((HttpsURLConnection) conn);
			httpsConn.setSSLSocketFactory(ssf);
			httpsConn.setRequestMethod(method.toString());
			result = URLConnectionRequest(httpsConn);
			httpsConn.disconnect();
		} else {
			HttpURLConnection httpConn = (HttpURLConnection) conn;
			httpConn.setRequestMethod(method.toString());
			result = URLConnectionRequest(httpConn);
			httpConn.disconnect();
		}
		return result;
	}

	/**
	 * 通用的请求方法
	 * @param conn
	 * @return
	 */
	private String URLConnectionRequest(final URLConnection conn) {
		conn.setConnectTimeout(5000);
		conn.setRequestProperty("User-Agent", userAgent);
		conn.setRequestProperty("Referer", protocol.toString().toLowerCase()+"://"+url.split("/")[2]);
		conn.setRequestProperty("Accept-Charset", reqCharset.name());
		conn.setUseCaches(false);
		if (Method.POST == method) {
			conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + reqCharset.name());
			conn.setDoOutput(true);
			conn.setDoInput(true);
			if (postData!=null) {
				try(OutputStream outputStream = conn.getOutputStream()){
					outputStream.write(postData.getBytes(reqCharset));
				}catch(IOException e){
					System.out.println(e.getMessage());
				}
			}
		} else {
			conn.setRequestProperty("Content-Type","text/html,application/xhtml+xml,application/xml,application/json,text/json;charset="+ reqCharset.name());
		}
		StringBuilder sb = new StringBuilder();
		if(savePath==null){
			try (InputStream inStream = conn.getInputStream();
					InputStreamReader inputStreamReader = new InputStreamReader(inStream, respCharset);
					BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
				String str = null;
				while ((str = bufferedReader.readLine()) != null)
					sb.append(str);
			} catch (IOException e) {
				System.out.println(e.getMessage());
			}
		}else{
			try(InputStream inStream = conn.getInputStream();
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			FileOutputStream fos = new FileOutputStream(savePath)){
			byte[] buffer = new byte[1024];
			int len = 0;
			while ((len = inStream.read(buffer)) != -1)
				bos.write(buffer, 0, len);
			fos.write(bos.toByteArray());
			sb.append("success");
			}catch(IOException e){
				System.out.println(e.getMessage());
			}
		}
		return sb.toString();
	}

	/**
	 * 创建类似于超链接的字符串，并将值urlEncode一下，key如果是中文那我无话可说
	 * @return 
	 */
	private String createParamStr() {
		StringBuilder sb = new StringBuilder();
		String charset = reqCharset.name();
		this.params.forEach((k, v) -> {
			if (v != null) {
				sb.append("&").append(k).append("=");
				try {
					sb.append(URLEncoder.encode(v.toString(), charset));
				} catch (UnsupportedEncodingException e) {
					sb.append(v);
				}
			}
		});
		return sb.length() > 0 ? sb.substring(1) : sb.toString();
	}
	/**
	 * 判断是否为微信访问
	 * @param request
	 * @return
	 */
	public static boolean isWechat(HttpServletRequest request){
		String ua = request.getHeader("User-Agent");
		return ua!=null&&ua.matches("(?i).*MicroMessenger.*");
	}
	
	/**
	 * 判断是否为手机访问
	 * @param request
	 * @return
	 */
	public static boolean isMobile(HttpServletRequest request){
		String ua = request.getHeader("User-Agent");
		return ua!=null&&(isWechat(request) || ua.matches("(?i).*(Android|Phone|iPod|iPad).*"));
	}
	
	@Override
	public void close() throws Exception {
		
	}
}
